<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>业务断言测试报告</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      body {
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, sans-serif;
        background-color: #f5f7fa;
        color: #333;
        line-height: 1.6;
      }

      .container {
        max-width: 1400px;
        margin: 0 auto;
        padding: 20px;
      }

      .header {
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        color: white;
        padding: 30px;
        border-radius: 12px;
        margin-bottom: 30px;
        box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
      }

      .header h1 {
        font-size: 2.5rem;
        margin-bottom: 10px;
      }

      .header .meta {
        font-size: 1rem;
        opacity: 0.9;
      }

      .summary-cards {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
        gap: 20px;
        margin-bottom: 30px;
      }

      .card {
        background: white;
        padding: 25px;
        border-radius: 12px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
        transition: transform 0.2s, box-shadow 0.2s;
      }

      .card:hover {
        transform: translateY(-2px);
        box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
      }

      .card-title {
        font-size: 0.9rem;
        color: #666;
        margin-bottom: 8px;
      }

      .card-value {
        font-size: 2rem;
        font-weight: bold;
        color: #333;
      }

      .card-subtitle {
        font-size: 0.85rem;
        color: #999;
        margin-top: 5px;
      }

      .card.success .card-value {
        color: #10b981;
      }

      .card.failed .card-value {
        color: #ef4444;
      }

      .card.warning .card-value {
        color: #f59e0b;
      }

      .charts-section {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
        gap: 20px;
        margin-bottom: 30px;
      }

      .chart-container {
        background: white;
        padding: 25px;
        border-radius: 12px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
      }

      .chart-title {
        font-size: 1.2rem;
        font-weight: 600;
        margin-bottom: 20px;
        color: #333;
      }

      .business-list {
        background: white;
        border-radius: 12px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
        overflow: hidden;
      }

      .business-header {
        background: #f8fafc;
        padding: 20px 25px;
        border-bottom: 1px solid #e5e7eb;
      }

      .business-header h2 {
        font-size: 1.5rem;
        color: #333;
      }

      .business-item {
        border-bottom: 1px solid #e5e7eb;
        transition: background-color 0.2s;
      }

      .business-item:last-child {
        border-bottom: none;
      }

      .business-item:hover {
        background-color: #f8fafc;
      }

      .business-summary {
        padding: 20px 25px;
        cursor: pointer;
        display: flex;
        justify-content: space-between;
        align-items: center;
      }

      .business-info {
        flex: 1;
      }

      .business-id {
        font-size: 1.1rem;
        font-weight: 600;
        margin-bottom: 5px;
      }

      .business-stats {
        font-size: 0.9rem;
        color: #666;
      }

      .business-status {
        padding: 6px 16px;
        border-radius: 20px;
        font-size: 0.9rem;
        font-weight: 500;
      }

      .status-success {
        background: #d1fae5;
        color: #065f46;
      }

      .status-failed {
        background: #fee2e2;
        color: #991b1b;
      }

      .status-error {
        background: #fef3c7;
        color: #92400e;
      }

      .business-details {
        display: none;
        padding: 0 25px 25px;
        background: #f8fafc;
      }

      .business-details.active {
        display: block;
      }

      .operation-list {
        margin-top: 20px;
      }

      .operation-item {
        background: white;
        border: 1px solid #e5e7eb;
        border-radius: 8px;
        margin-bottom: 15px;
        overflow: hidden;
      }

      .operation-header {
        padding: 15px 20px;
        display: flex;
        justify-content: space-between;
        align-items: center;
        cursor: pointer;
        transition: background-color 0.2s;
      }

      .operation-header:hover {
        background-color: #f3f4f6;
      }

      .operation-title {
        display: flex;
        align-items: center;
        gap: 10px;
      }

      .operation-icon {
        font-size: 1.2rem;
      }

      .operation-name {
        font-weight: 600;
      }

      .operation-assertions {
        display: none;
        padding: 0 20px 20px;
        background: #fafbfc;
      }

      .operation-assertions.active {
        display: block;
      }

      .assertion-item {
        padding: 12px 0;
        border-bottom: 1px solid #e5e7eb;
        display: flex;
        align-items: flex-start;
        gap: 10px;
      }

      .assertion-item:last-child {
        border-bottom: none;
      }

      .assertion-icon {
        font-size: 1.2rem;
        margin-top: 2px;
      }

      .assertion-icon.success {
        color: #10b981;
      }

      .assertion-icon.failed {
        color: #ef4444;
      }

      .assertion-content {
        flex: 1;
      }

      .assertion-type {
        font-weight: 600;
        margin-bottom: 4px;
      }

      .assertion-message {
        font-size: 0.9rem;
        color: #666;
        margin-bottom: 4px;
      }

      .assertion-values {
        font-size: 0.85rem;
        color: #999;
      }

      .file-upload {
        background: white;
        padding: 30px;
        border-radius: 12px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
        text-align: center;
        margin-bottom: 30px;
      }

      .file-input {
        display: none;
      }

      .file-label {
        display: inline-block;
        padding: 12px 30px;
        background: #667eea;
        color: white;
        border-radius: 8px;
        cursor: pointer;
        transition: background-color 0.2s;
      }

      .file-label:hover {
        background: #5a67d8;
      }

      .loading {
        text-align: center;
        padding: 50px;
        color: #666;
      }

      .error {
        background: #fee2e2;
        color: #991b1b;
        padding: 20px;
        border-radius: 8px;
        margin: 20px 0;
      }

      canvas {
        max-width: 100%;
        height: auto !important;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <!-- 文件上传区域 -->
      <div class="file-upload">
        <input type="file" id="fileInput" class="file-input" accept=".json" />
        <label for="fileInput" class="file-label">选择JSON文件</label>
        <p style="margin-top: 10px; color: #666">
          或将assertion_results_*.json文件拖放到此处
        </p>
      </div>

      <!-- 主内容区域 -->
      <div id="content" style="display: none">
        <!-- 头部信息 -->
        <div class="header">
          <h1>业务断言测试报告</h1>
          <div class="meta" id="executionTime"></div>
        </div>

        <!-- 摘要卡片 -->
        <div class="summary-cards" id="summaryCards"></div>

        <!-- 图表区域 -->
        <div class="charts-section">
          <div class="chart-container">
            <h3 class="chart-title">操作类型统计</h3>
            <canvas id="operationChart"></canvas>
          </div>
          <div class="chart-container">
            <h3 class="chart-title">断言类型统计</h3>
            <canvas id="assertionChart"></canvas>
          </div>
        </div>

        <!-- 业务列表 -->
        <div class="business-list">
          <div class="business-header">
            <h2>业务详情</h2>
          </div>
          <div id="businessList"></div>
        </div>
      </div>

      <!-- 加载提示 -->
      <div id="loading" class="loading" style="display: none">
        正在加载数据...
      </div>

      <!-- 错误提示 -->
      <div id="error" class="error" style="display: none"></div>
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
    <script>
      let testData = null;
      let operationChart = null;
      let assertionChart = null;

      // 文件选择处理
      document
        .getElementById("fileInput")
        .addEventListener("change", handleFileSelect);

      // 拖放处理
      document.addEventListener("dragover", (e) => {
        e.preventDefault();
        e.stopPropagation();
      });

      document.addEventListener("drop", (e) => {
        e.preventDefault();
        e.stopPropagation();

        const files = e.dataTransfer.files;
        if (files.length > 0 && files[0].type === "application/json") {
          handleFile(files[0]);
        }
      });

      function handleFileSelect(event) {
        const file = event.target.files[0];
        if (file && file.type === "application/json") {
          handleFile(file);
        }
      }

      function handleFile(file) {
        const reader = new FileReader();
        reader.onload = function (e) {
          try {
            testData = JSON.parse(e.target.result);
            renderDashboard();
          } catch (error) {
            showError("JSON文件解析失败：" + error.message);
          }
        };
        reader.readAsText(file);
      }

      function renderDashboard() {
        document.getElementById("content").style.display = "block";
        document.getElementById("loading").style.display = "none";
        document.getElementById("error").style.display = "none";

        // 渲染头部信息
        document.getElementById(
          "executionTime"
        ).textContent = `执行时间：${testData.metadata.execution_date} | 总业务数：${testData.metadata.total_businesses}`;

        // 渲染摘要卡片
        renderSummaryCards();

        // 渲染图表
        renderCharts();

        // 渲染业务列表
        renderBusinessList();
      }

      function renderSummaryCards() {
        const summaryCards = document.getElementById("summaryCards");
        summaryCards.innerHTML = `
                <div class="card">
                    <div class="card-title">总业务数</div>
                    <div class="card-value">${
                      testData.metadata.total_businesses
                    }</div>
                </div>
                <div class="card success">
                    <div class="card-title">成功业务</div>
                    <div class="card-value">${
                      testData.metadata.successful_businesses
                    }</div>
                    <div class="card-subtitle">${
                      testData.summary.by_status.success
                    } 个</div>
                </div>
                <div class="card failed">
                    <div class="card-title">失败业务</div>
                    <div class="card-value">${
                      testData.metadata.failed_businesses
                    }</div>
                    <div class="card-subtitle">${
                      testData.summary.by_status.failed
                    } 个</div>
                </div>
                <div class="card warning">
                    <div class="card-title">业务成功率</div>
                    <div class="card-value">${testData.metadata.business_success_rate.toFixed(
                      1
                    )}%</div>
                </div>
            `;
      }

      function renderCharts() {
        // 操作类型图表
        const operationCtx = document
          .getElementById("operationChart")
          .getContext("2d");
        const operationData = testData.summary.by_operation;
        const operationLabels = Object.keys(operationData);

        if (operationChart) operationChart.destroy();

        operationChart = new Chart(operationCtx, {
          type: "bar",
          data: {
            labels: operationLabels,
            datasets: [
              {
                label: "成功",
                data: operationLabels.map(
                  (type) => operationData[type].success
                ),
                backgroundColor: "#10b981", // 绿色
              },
              {
                label: "失败",
                data: operationLabels.map(
                  (type) =>
                    operationData[type].total - operationData[type].success
                ),
                backgroundColor: "#ef4444", // 红色
              },
            ],
          },
          options: {
            responsive: true,
            plugins: {
              legend: { position: "top" },
              tooltip: {
                callbacks: {
                  label: (context) => {
                    const type = operationLabels[context.dataIndex];
                    const data = operationData[type];
                    return [
                      `${context.dataset.label}: ${context.raw}`,
                      `成功率: ${data.success_rate}%`,
                    ];
                  },
                },
              },
            },
            scales: {
              y: { beginAtZero: true },
            },
          },
        });

        // 断言类型图表（保持不变）
        const assertionCtx = document
          .getElementById("assertionChart")
          .getContext("2d");
        const assertionData = testData.summary.by_assertion;

        if (assertionChart) assertionChart.destroy();

        assertionChart = new Chart(assertionCtx, {
          type: "doughnut",
          data: {
            labels: Object.keys(assertionData).map((key) => {
              const typeMap = {
                status_code: "状态码断言",
                response_time: "响应时间断言",
                data_consistency: "数据一致性断言",
                error_message: "错误信息断言",
              };
              return typeMap[key] || key;
            }),
            datasets: [
              {
                data: Object.values(assertionData).map((d) => d.total),
                backgroundColor: ["#667eea", "#10b981", "#f59e0b", "#ef4444"],
              },
            ],
          },
          options: {
            responsive: true,
            plugins: {
              legend: { position: "right" },
            },
          },
        });
      }

      function renderBusinessList() {
        const businessList = document.getElementById("businessList");
        businessList.innerHTML = "";

        testData.businesses.forEach((business, index) => {
          const businessItem = document.createElement("div");
          businessItem.className = "business-item";

          businessItem.innerHTML = `
                    <div class="business-summary" onclick="toggleBusiness(${index})">
                        <div class="business-info">
                            <div class="business-id">业务 ID: ${
                              business.record_id
                            }</div>
                            <div class="business-stats">
                                操作数: ${business.summary.total_operations} | 
                                断言数: ${business.summary.total_assertions} | 
                                通过率: ${business.summary.success_rate}%
                            </div>
                        </div>
                        <div class="business-status status-${business.status}">
                            ${business.status_text}
                        </div>
                    </div>
                    <div class="business-details" id="business-${index}">
                        ${renderBusinessDetails(business)}
                    </div>
                `;

          businessList.appendChild(businessItem);
        });
      }

      function renderBusinessDetails(business) {
        if (business.status === "error") {
          return `<div class="error">${business.error_message}</div>`;
        }

        let html = '<div class="operation-list">';

        business.operations.forEach((operation, opIndex) => {
          html += `
                    <div class="operation-item">
                        <div class="operation-header" onclick="toggleOperation('${
                          business.record_id
                        }-${opIndex}')">
                            <div class="operation-title">
                                <span class="operation-icon">${
                                  operation.operation_icon
                                }</span>
                                <span class="operation-name">${
                                  operation.case_name
                                }</span>
                            </div>
                            <div class="operation-status status-${
                              operation.success ? "success" : "failed"
                            }">
                                ${operation.status_text}
                            </div>
                        </div>
                        <div class="operation-assertions" id="op-${
                          business.record_id
                        }-${opIndex}">
                            <div style="padding: 15px 0;">
                                <div style="margin-bottom: 10px; color: #666;">
                                    <strong>描述：</strong>${
                                      operation.description
                                    }<br>
                                    <strong>预期结果：</strong>${
                                      operation.expected_result
                                    }
                                </div>
                                ${renderAssertions(operation.assertions)}
                            </div>
                        </div>
                    </div>
                `;
        });

        // 添加数据一致性检查结果
        if (
          business.consistency_checks &&
          business.consistency_checks.length > 0
        ) {
          html +=
            '<h4 style="margin-top: 20px; margin-bottom: 10px;">数据一致性检查</h4>';
          html += '<div class="operation-list">';
          business.consistency_checks.forEach((check) => {
            html += `
                        <div class="assertion-item">
                            <span class="assertion-icon ${
                              check.success ? "success" : "failed"
                            }">
                                ${check.success ? "✓" : "✗"}
                            </span>
                            <div class="assertion-content">
                                <div class="assertion-type">${
                                  check.description
                                }</div>
                                <div class="assertion-message">${
                                  check.message
                                }</div>
                            </div>
                        </div>
                    `;
          });
          html += "</div>";
        }

        html += "</div>";
        return html;
      }

      function renderAssertions(assertions) {
        let html = "";
        assertions.forEach((assertion) => {
          html += `
                    <div class="assertion-item">
                        <span class="assertion-icon ${
                          assertion.success ? "success" : "failed"
                        }">
                            ${assertion.status_icon}
                        </span>
                        <div class="assertion-content">
                            <div class="assertion-type">${
                              assertion.type_text
                            }</div>
                            <div class="assertion-message">${
                              assertion.message
                            }</div>
                            ${
                              assertion.expected || assertion.actual
                                ? `
                                <div class="assertion-values">
                                    ${
                                      assertion.expected
                                        ? `期望: ${JSON.stringify(
                                            assertion.expected
                                          )}`
                                        : ""
                                    }
                                    ${
                                      assertion.expected && assertion.actual
                                        ? " | "
                                        : ""
                                    }
                                    ${
                                      assertion.actual
                                        ? `实际: ${JSON.stringify(
                                            assertion.actual
                                          )}`
                                        : ""
                                    }
                                </div>
                            `
                                : ""
                            }
                        </div>
                    </div>
                `;
        });
        return html;
      }

      function toggleBusiness(index) {
        const details = document.getElementById(`business-${index}`);
        details.classList.toggle("active");
      }

      function toggleOperation(id) {
        const assertions = document.getElementById(`op-${id}`);
        assertions.classList.toggle("active");
        event.stopPropagation();
      }

      function showError(message) {
        document.getElementById("error").textContent = message;
        document.getElementById("error").style.display = "block";
        document.getElementById("content").style.display = "none";
        document.getElementById("loading").style.display = "none";
      }

      // 示例数据（用于演示）
      const demoData = {
        metadata: {
          execution_time: "2025-07-10T14:42:00",
          execution_date: "2025-07-10 14:42:00",
          total_businesses: 4,
          successful_businesses: 3,
          failed_businesses: 1,
          error_businesses: 0,
          business_success_rate: 75.0,
        },
        summary: {
          by_status: {
            success: 3,
            failed: 1,
            error: 0,
          },
          by_operation: {
            create: {
              total: 5,
              success: 4,
              success_rate: 80.0,
            },
            read: {
              total: 12,
              success: 12,
              success_rate: 100.0,
            },
            update: {
              total: 4,
              success: 2,
              success_rate: 50.0,
            },
            delete: {
              total: 6,
              success: 4,
              success_rate: 66.7,
            },
          },
          by_assertion: {
            status_code: {
              total: 27,
              passed: 25,
              success_rate: 92.6,
            },
            response_time: {
              total: 27,
              passed: 27,
              success_rate: 100.0,
            },
            error_message: {
              total: 5,
              passed: 5,
              success_rate: 100.0,
            },
          },
        },
        businesses: [
          {
            record_id: "1837147714514985",
            status: "success",
            status_text: "成功",
            summary: {
              total_operations: 6,
              successful_operations: 6,
              total_assertions: 15,
              passed_assertions: 15,
              success_rate: 100.0,
            },
            operation_breakdown: [
              {
                type: "create",
                total: 1,
                success: 1,
                success_rate: 100.0,
              },
              {
                type: "read",
                total: 3,
                success: 3,
                success_rate: 100.0,
              },
              {
                type: "delete",
                total: 2,
                success: 2,
                success_rate: 100.0,
              },
            ],
            operations: [
              {
                index: 1,
                operation_type: "create",
                operation_icon: "➕",
                case_name: "创建记录-正常数据v1.0",
                description: "使用有效数据创建新记录",
                expected_result: "创建成功，返回记录ID",
                success: true,
                status_text: "通过",
                assertions: [
                  {
                    type: "status_code",
                    type_text: "状态码断言",
                    success: true,
                    status_icon: "✓",
                    message:
                      "状态断言通过：预期成功，实际成功 (数据类型为normal，预期操作成功)",
                    description:
                      "验证 创建记录-正常数据v1.0 的执行结果 - 使用有效数据创建新记录",
                    expected: { success: true, code: "0" },
                    actual: { success: true, code: "0" },
                  },
                  {
                    type: "response_time",
                    type_text: "响应时间断言",
                    success: true,
                    status_icon: "✓",
                    message: "响应时间符合预期：1153.6ms <= 2000ms",
                    description: "验证 创建记录-正常数据v1.0 的响应时间",
                    expected: "<= 2000ms",
                    actual: "1153.6ms",
                  },
                ],
              },
            ],
            consistency_checks: [
              {
                type: "data_consistency",
                success: true,
                message: "创建后的读取操作成功返回数据",
                description: "验证创建操作后数据可以被正确读取",
              },
            ],
          },
        ],
      };

      // 加载示例数据
      // testData = demoData;
      // 假设 assertion_results.json 放在 public/data/assertion_results.json
      fetch("/data/assertion_results.json")
        .then((response) => response.json())
        .then((data) => {
          testData = data;
          renderDashboard();
        })
        .catch((error) => {
          console.error("加载 JSON 失败:", error);
        });
    </script>
  </body>
</html>
